package utils

import (
	"fmt"
	"reflect"
	"regexp"
	"strings"
	"sync"

	"github.com/go-playground/validator/v10"
)

var validate *validator.Validate

func GetValidate() *validator.Validate {
	if nil != validate {
		return validate
	}
	validate = validator.New()
	validate.RegisterValidation("required_ifBitMatch", requiredIfbitMatch)
	return validate
}

func requiredIfbitMatch(fl validator.FieldLevel) bool {
	params := parseOneOfParam2(fl.Param())
	if len(params)%2 != 0 {
		Debug(fmt.Sprintf("Bad param number for required_ifBitMatch %s", fl.FieldName()))
		return false
	}

	for i := 0; i < len(params); i += 2 {
		key := params[i]
		value := params[i+1]
		bitValue, err := ParseBitPhrase(value)
		if err != nil {
			Debug(err.Error())
			return false
		}
		field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), key)
		if !found {
			return false
		}
		switch kind {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			if field.Int()&bitValue != bitValue {
				return true
			}

		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			if int64(field.Uint())&bitValue != bitValue {
				return true
			}
		default:
			return false
		}
	}
	return hasValue(fl)
}

// hasValue is the validation function for validating if the current field's value is not the default static value.
func hasValue(fl validator.FieldLevel) bool {
	field := fl.Field()

	switch field.Kind() {
	case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:

		return !field.IsNil()
	case reflect.Invalid:
		return false
	default:
		if _, _, nullable := extractTypeInternal(field, false); nullable && field.Interface() != nil {
			return true
		}
		return field.IsValid() && field.Interface() != reflect.Zero(field.Type()).Interface()
	}
}

func extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
BEGIN:
	switch current.Kind() {
	case reflect.Ptr:

		nullable = true

		if current.IsNil() {
			return current, reflect.Ptr, nullable
		}

		current = current.Elem()
		goto BEGIN

	case reflect.Interface:

		nullable = true

		if current.IsNil() {
			return current, reflect.Interface, nullable
		}

		current = current.Elem()
		goto BEGIN

	default:
		return current, current.Kind(), nullable
	}
}

var (
	oneofValsCache       = map[string][]string{}
	oneofValsCacheRWLock = sync.RWMutex{}
	splitParamsRegex     = regexp.MustCompile(`'[^']*'|\S+`)
)

func parseOneOfParam2(s string) []string {
	oneofValsCacheRWLock.RLock()
	vals, ok := oneofValsCache[s]
	oneofValsCacheRWLock.RUnlock()
	if !ok {
		oneofValsCacheRWLock.Lock()
		vals = splitParamsRegex.FindAllString(s, -1)
		for i := 0; i < len(vals); i++ {
			vals[i] = strings.Replace(vals[i], "'", "", -1)
		}
		oneofValsCache[s] = vals
		oneofValsCacheRWLock.Unlock()
	}
	return vals
}
